/*----------------------------------------------------------------------
 *
 * Copyright 2009, Thomas Dejanovic.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 *---------------------------------------------------------------------
 *
 * Low Pin Count Bus splitter / combiner module
 *
 *---------------------------------------------------------------------*/


module lpcb_split_2x

  (/*AUTOARG*/
  // Outputs
  lpcb_frame_out, lpcb_cad_out, lpcb_frame_a_out, lpcb_cad_a_out,
  lpcb_frame_b_out, lpcb_cad_b_out,
  // Inputs
  clk, clk_en, reset, lpcb_frame_in, lpcb_cad_in, lpcb_frame_a_in,
  lpcb_cad_a_in, lpcb_frame_b_in, lpcb_cad_b_in
  );

  //----------------------------------------
  input                 clk;
  input                 clk_en;

  input                 reset;
  //----------------------------------------

  //----------------------------------------
  // lpcb interface - root
  input                 lpcb_frame_in;
  input [3:0]           lpcb_cad_in;

  output                lpcb_frame_out;
  output [3:0]          lpcb_cad_out;
  //----------------------------------------

  //----------------------------------------
  // lpcb interface - branch a.
  output                lpcb_frame_a_out;
  output [3:0]          lpcb_cad_a_out;

  input                 lpcb_frame_a_in;
  input [3:0]           lpcb_cad_a_in;
  //----------------------------------------

  //----------------------------------------
  // lpcb interface - branch b.
  output                lpcb_frame_b_out;
  output [3:0]          lpcb_cad_b_out;

  input                 lpcb_frame_b_in;
  input [3:0]           lpcb_cad_b_in;
  //----------------------------------------

  /*----------------------------------------------------------------*/

  /*-AUTOUNUSED-*/

  /*AUTOINPUT*/

  /*AUTOOUTPUT*/

  /*-AUTOREGINPUT-*/

  /*AUTOREG*/

  /*AUTOWIRE*/

  /*------------------------------------------------------------------
   *
   * local definitions and connections.
   *
   * */

`include "lpcb_defines.vh"

  /*------------------------------------------------------------------
   *
   * pipeline the incomming data to make timing easier.
   *
   * */

  reg                   lpcb_frame_a_int;
  reg [3:0]             lpcb_cad_a_int;
  reg                   lpcb_frame_b_int;
  reg [3:0]             lpcb_cad_b_int;
  always @ (posedge clk) if (clk_en) begin
    if (reset) begin
      lpcb_frame_a_int <= 1'd0;
      lpcb_cad_a_int   <= 4'd0;
      lpcb_frame_b_int <= 1'd0;
      lpcb_cad_b_int   <= 4'd0;
    end else begin
      lpcb_frame_a_int <= lpcb_frame_a_in;
      lpcb_cad_a_int   <= lpcb_cad_a_in;
      lpcb_frame_b_int <= lpcb_frame_b_in;
      lpcb_cad_b_int   <= lpcb_cad_b_in;
    end
  end

  /*------------------------------------------------------------------
   *
   * outgoing branch is simple.
   *
   * */

  reg                   lpcb_frame_a_out;
  reg [3:0]             lpcb_cad_a_out;
  reg                   lpcb_frame_b_out;
  reg [3:0]             lpcb_cad_b_out;
  always @ (posedge clk) if (clk_en) begin
    if (reset) begin
      lpcb_frame_a_out <= 1'd0;
      lpcb_cad_a_out   <= 4'd0;
      lpcb_frame_b_out <= 1'd0;
      lpcb_cad_b_out   <= 4'd0;
    end else begin
      lpcb_frame_a_out <= lpcb_frame_int;
      lpcb_cad_a_out   <= lpcb_cad_int;
      lpcb_frame_b_out <= lpcb_frame_int;
      lpcb_cad_b_out   <= lpcb_cad_int;
    end
  end

  /*------------------------------------------------------------------
   *
   * combining branches is a little more interesting.
   *
   * */

  reg                   current_branch;
  reg [4:0]             count;
  reg                   lpcb_frame_out;
  reg [3:0]             lpcb_cad_out;

  always @ (posedge clk) if (clk_en) begin
    if (reset) begin
      current_branch <= 1'd0;
      count          <= 5'd0;
      lpcb_frame_out <= 1'd0;
      lpcb_cad_out   <= 4'd0;
    end else begin
      if (count > 5'd0) begin
        // we are processing a command.
        if (current_branch == 1'd0) begin
          // we are processing a command on branch a.
          if (lpcb_frame_b_int && (lpcb_cad_b_int == `LPCB_READ_ACK_CMD)) begin
            // we have a collison, take the incomming read over
            // anything else, otherwise ignore the colliding command.
            lpcb_frame_out <= 1'd1;
            lpcb_cad_out   <= lpcb_cad_b_int;
            count          <= 5'd8;
          end else begin

            XXXXX this needs to eb cleaned up.  I'm sure we only need
            to look up the cad to count thinc a couple of times rather
            than the current 4.

            if (lpcb_frame_a_int) begin
              // we have a colision on the current port.
              case(lpcb_cad_a_int)
                `LPCB_WRITE_8B_CMD  : count <= 5'd9;  // 2 addr + 8 data - 1
                `LPCB_WRITE_16B_CMD : count <= 5'd11; // 4 addr + 8 data - 1
                `LPCB_WRITE_24B_CMD : count <= 5'd13; // 6 addr + 8 data - 1
                `LPCB_WRITE_32B_CMD : count <= 5'd16; // 8 addr + 8 data - 1
                `LPCB_READ_8B_CMD   : count <= 5'd1;  // 2 addr - 1
                `LPCB_READ_16B_CMD  : count <= 5'd3;  // 4 addr - 1
                `LPCB_READ_24B_CMD  : count <= 5'd5;  // 6 addr - 1
                `LPCB_READ_32B_CMD  : count <= 5'd7;  // 8 addr - 1
                `LPCB_READ_ACK_CMD  : count <= 5'd7;  // 8 data - 1
                default             : count <= 5'd0;
              endcase // case(lpcb_cad_a_int)
            end else begin
              count          <= count - 5'd1;
            end
            lpcb_cad_out   <= lpcb_cad_a_int;
            lpcb_frame_out <= lpcb_frame_a_int;
          end
        end else begin
          // we are processing a command on branch b.
          if (lpcb_frame_a_int && (lpcb_cad_a_int == `LPCB_READ_ACK_CMD)) begin
            // we have a collison, take the incomming read over
            // anything else, otherwise ignore the colliding command.
            lpcb_frame_out <= 1'd1;
            lpcb_cad_out   <= lpcb_cad_a_int;
            count          <= 5'd7;
          end else begin
            if (lpcb_frame_b_int) begin
              // we have a colision on the current port.
              case(lpcb_cad_b_int)
                `LPCB_WRITE_8B_CMD  : count <= 5'd10;
                `LPCB_WRITE_16B_CMD : count <= 5'd12;
                `LPCB_WRITE_24B_CMD : count <= 5'd14;
                `LPCB_WRITE_32B_CMD : count <= 5'd16;
                `LPCB_READ_8B_CMD   : count <= 5'd8;
                `LPCB_READ_16B_CMD  : count <= 5'd12;
                `LPCB_READ_24B_CMD  : count <= 5'd14;
                `LPCB_READ_32B_CMD  : count <= 5'd16;
                `LPCB_READ_ACK_CMD  : count <= 5'd8;
                default             : count <= 5'd0;
              endcase // case(lpcb_cad_a_int)
            end else begin
              count          <= count - 5'd1;
            end
            lpcb_cad_out   <= lpcb_cad_b_int;
            lpcb_frame_out <= lpcb_frame_b_int;
          end
        end
      end else begin // if (count > 5'd0)
        if (lpcb_frame_a_int) begin
          // we have an incomming command.
          current_branch <= 1'd0;
          case(lpcb_cad_a_int)
            `LPCB_WRITE_8B_CMD  : count <= 5'd10;
            `LPCB_WRITE_16B_CMD : count <= 5'd12;
            `LPCB_WRITE_24B_CMD : count <= 5'd14;
            `LPCB_WRITE_32B_CMD : count <= 5'd16;
            `LPCB_READ_8B_CMD   : count <= 5'd8;
            `LPCB_READ_16B_CMD  : count <= 5'd12;
            `LPCB_READ_24B_CMD  : count <= 5'd14;
            `LPCB_READ_32B_CMD  : count <= 5'd16;
            `LPCB_READ_ACK_CMD  : count <= 5'd8;
            default             : count <= 5'd0;
          endcase // case(lpcb_cad_a_int)
          lpcb_cad_out   <= lpcb_cad_a_int;
          lpcb_frame_out <= lpcb_frame_a_int;
        end else if (lpcb_frame_b_int) begin
          // we have an incomming command.
          current_branch <= 1'd1;
          case(lpcb_cad_b_int)
            `LPCB_WRITE_8B_CMD  : count <= 5'd10;
            `LPCB_WRITE_16B_CMD : count <= 5'd12;
            `LPCB_WRITE_24B_CMD : count <= 5'd14;
            `LPCB_WRITE_32B_CMD : count <= 5'd16;
            `LPCB_READ_8B_CMD   : count <= 5'd8;
            `LPCB_READ_16B_CMD  : count <= 5'd12;
            `LPCB_READ_24B_CMD  : count <= 5'd14;
            `LPCB_READ_32B_CMD  : count <= 5'd16;
            `LPCB_READ_ACK_CMD  : count <= 5'd8;
            default             : count <= 5'd0;
          endcase // case(lpcb_cad_b_int)
          lpcb_cad_out   <= lpcb_cad_b_int;
          lpcb_frame_out <= lpcb_frame_b_int;
        end else begin
          // keep the upstream bus quiet if we don't think there is a
          // command on the bus.
          lpcb_frame_out <= 1'd0;
          lpcb_cad_out   <= 4'd0;
        end
      end
    end
  end

  /*----------------------------------------------------------------*/

endmodule // lpcb_split_2x


// Local Variables:
// verilog-library-directories:(".")
// verilog-library-extensions:(".v")
// End:
